/**
 * BuddyList.java 2008-05-14
 * 
 * Linker is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

package linker.buddy;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.RosterPacket;

import linker.LocalVariablesManager;
import linker.Settings;
import linker.account.Account;
import linker.gui.BuddyInfoWindow;
import linker.gui.ChatWindow;
import linker.list.ListItem;
import linker.list.Listmodel;
import linker.list.ViewList;
import linker.message.SubscribeRequest;
import linker.sound.SoundPlayer;
import linker.status.StatusIcons;

/**
 * This class is the list of your buddies, which saves your current contacts
 * information. The class also provides you with the function of adding,
 * deleting and showing your buddies.
 * 
 * @version 2008-05-31
 * @author Jianfeng tujf.cn@gmail.com
 * @author AwakenRz awakenrz@gmail.com
 * 
 */

public class BuddyList extends Listmodel {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * The ChatCellProxy list.
	 */
	private Vector<BuddyItem> list = new Vector<BuddyItem>();

	/**
	 * The map of BuddyItem and BuddyChat.
	 */
	private HashMap<BuddyItem, BuddyChat> map;

	/**
	 * 
	 */
	private HashMap<BuddyItem, String> userMap;

	/**
	 * 
	 */
	private HashMap<BuddyItem, Chat> chatMap;

	/**
	 * The user's roster.
	 */
	private Roster roster;

	/**
	 * The last refilter string.
	 */
	private String last = "";

	/**
	 * The chatManager.
	 */
	private static ChatManager chatManager;

	/**
	 * The flag to show the online only buddy.
	 */
	private boolean onlineOnly = false;

	/**
	 * The flag to show sound or not.
	 */
	private Boolean soundNotify;

	/**
	 * 
	 * @param connection
	 *            The XMPPConnection used by the current user.
	 */
	public BuddyList(final XMPPConnection connection) {
		map = new HashMap<BuddyItem, BuddyChat>();
		userMap = new HashMap<BuddyItem, String>();
		chatMap = new HashMap<BuddyItem, Chat>();

		roster = connection.getRoster();
		chatManager = connection.getChatManager();
		chatManager.addChatListener(new ChatManagerListener() {
			public void chatCreated(final Chat chat, final boolean create) {
				saveChat(chat);
			}
		});

		soundNotify = Boolean.valueOf(Settings.getSettings().getValue(
				"soundnotify"));
	}

	/**
	 * Create popupMenu.
	 * 
	 * @param listItem
	 *            The select list item.
	 * @return The created popupMenu
	 */
	public final JPopupMenu getPopupMenuByItem(final ListItem listItem) {

		JPopupMenu popupMenu = new JPopupMenu();
		ActionListener listener = getActionListener(popupMenu, listItem);

		JMenuItem item = new JMenuItem(LocalVariablesManager
				.getLocalVariables("ChatCell.PopupMenu.Chat"));
		item.addActionListener(listener);
		popupMenu.add(item);
		item = new JMenuItem(LocalVariablesManager
				.getLocalVariables("ChatCell.PopupMenu.Info"));
		item.addActionListener(listener);
		popupMenu.add(item);
		item = new JMenuItem(LocalVariablesManager
				.getLocalVariables("ChatCell.PopupMenu.Delete"));
		item.addActionListener(listener);
		popupMenu.add(item);

		return popupMenu;
	}

	/**
	 * Get the actionListener to add to the JPopupMenu.
	 * 
	 * @param menu
	 *            The JPopupmenu to add this actionlistener.
	 * @param listItem
	 *            The ListItem.
	 * @return The actionslistener.
	 */
	public final ActionListener getActionListener(final JPopupMenu menu,
			final ListItem listItem) {
		final BuddyItem buddyItem = (BuddyItem) listItem;

		ActionListener listener = new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				if (e.getActionCommand() == LocalVariablesManager
						.getLocalVariables("ChatCell.PopupMenu.Chat")) {
					listItem.doubleClick();
				} else if (e.getActionCommand() == LocalVariablesManager
						.getLocalVariables("ChatCell.PopupMenu.Info")) {
					new BuddyInfoWindow(buddyItem.getBuddyChat());
				} else if (e.getActionCommand() == LocalVariablesManager
						.getLocalVariables("ChatCell.PopupMenu.Delete")) {
					deleteElement(buddyItem.getEntry().getUser());
				}

				menu.setVisible(false);
			}
		};

		return listener;
	}

	/**
	 * 
	 * @param item
	 *            The item double clicked.
	 */
	public final void doubleClickByItem(final ListItem item) {
		item.doubleClick();
	}

	/**
	 * Add a buddy to the buddyList.
	 * 
	 * @param entry
	 *            The buddy's entry.
	 */
	public final void addBuddy(final RosterEntry entry) {
		BuddyItem buddyItem = new BuddyItem(entry, this);
		userMap.put(buddyItem, entry.getUser());
		list.addElement(buddyItem);
	}

	/**
	 * Add buddy to the roster.
	 * 
	 * @param user
	 *            The user id of the buddy to be added.
	 */
	public final void addElement(final String user) {
		//
		int i = JOptionPane.showConfirmDialog(null, LocalVariablesManager
				.getLocalVariables("SubscribeConfirm.Info")
				+ user + "?", LocalVariablesManager
				.getLocalVariables("SubscribeConfirm.Title"),
				JOptionPane.YES_NO_OPTION);

		if (i == JOptionPane.OK_OPTION) {

			// Confirm to add a new buddy.
			Presence presence = new Presence(Presence.Type.subscribe);
			// Here need the Username with full smack ID
			// So, we add getUserFullID() method in Account.java
			presence.setFrom(Account.getUserFullID());
			presence.setTo(user);
			Account.sendPacket(presence);
		}
	}

	/**
	 * Unsubscribe to someone.
	 * 
	 * @param user
	 *            The user to be deleted.
	 * 
	 */
	public final void deleteElement(final String user) {
		int i = JOptionPane.showConfirmDialog(null, LocalVariablesManager
				.getLocalVariables("UnsubscribeConfirm.Info")
				+ user + "?", LocalVariablesManager
				.getLocalVariables("UnsubscribeConfirm.Title"),
				JOptionPane.YES_NO_OPTION);

		if (i == JOptionPane.OK_OPTION) {

			// Confirm to delete a new buddy.
			Presence presence = new Presence(Presence.Type.unsubscribe);
			// Here need the Username with full smack ID
			// So, we add getUserFullID() method in Account.java
			presence.setFrom(Account.getUserFullID());
			presence.setTo(user);
			Account.sendPacket(presence);
		}
	}

	/**
	 * Do the received presence.deliver this to the current chatCell.
	 * 
	 * @param presence
	 *            The received presence.
	 */
	public final void setPresence(final Presence presence) {
		String from = presence.getFrom();

		// Get all buddies if there is no buddy in the list.
		if (list.size() == 0) {
			initialByRoster(roster);
		}

		// Handle different types of presence
		if (presence.getType() == Presence.Type.subscribe) {
			// is subscribe
			// Someone wants to subscribe to you
			ViewList.addMessage(new SubscribeRequest(from));
		} else if (presence.getType() == Presence.Type.subscribed) {
			initialByRoster(roster);
		} else if (presence.getType() == Presence.Type.unsubscribed) {
			initialByRoster(roster);
		} else {
			// The buddy change presence The info of your buddies change
			// Skip unavailable presence.
			if (presence.getType() == Presence.Type.unavailable) {
				return;
			}

			for (int i = 0; i < list.size(); i++) {
				if (from.indexOf(userMap.get(list.elementAt(i))) >= 0) {

					BuddyItem buddyItem = list.elementAt(i);
					BuddyChat buddyChat = map.get(buddyItem);
					if (buddyChat == null) {
						buddyChat = new BuddyChat(buddyItem);
						map.put(buddyItem, buddyChat);
					}
					buddyChat.setPresence(presence);

					Presence.Mode mode = presence.getMode();
					Presence.Type type = presence.getType();

					// Change status icon by presence type
					ImageIcon statusIcon;
					if (type == null || type != Presence.Type.available) {
						statusIcon = StatusIcons.getOffIcon();
					} else if (mode == Presence.Mode.away) {
						statusIcon = StatusIcons.getAwayIcon();
					} else if (mode == Presence.Mode.dnd) {
						statusIcon = StatusIcons.getBusyIcon();
					} else {
						statusIcon = StatusIcons.getFreeIcon();
					}

					// Set chatWindow's icon if it's not null;
					ChatWindow chatWindow = buddyChat.getChatWindow();
					if (chatWindow != null) {
						chatWindow.setIconImage(statusIcon.getImage());
					}

					buddyItem.setTitle(buddyChat.getName()
							+ buddyChat.getStatus());
					buddyItem.setIcon(statusIcon);
				}
			}
		}
		refilter(null);
	}

	/**
	 * Refresh the elements of the buddylist when you add buddies, change
	 * buddy's local name or whenever your buddy list changes.
	 * 
	 * @param s
	 *            The refilter string.
	 */
	public final void refilter(final String s) {

		// If s is null. Use last argument.
		if (s != null) {
			last = s;
		}

		// Clear the display list
		clear();

		// Add chatCell fit the request.
		BuddyItem buddyItem;
		boolean include;
		for (int i = 0; i < list.size(); i++) {
			buddyItem = list.elementAt(i);

			include = buddyItem.toString().indexOf(last) >= 0
					|| userMap.get(buddyItem).indexOf(last) >= 0;
			if (include && !buddyItem.isOffline()) {
				addElement(buddyItem);
			}
		}

		// Judge whether online only or not. If true, skip out.
		if (onlineOnly) {
			return;
		}

		// Add offline buddy.
		for (int i = 0; i < list.size(); i++) {
			buddyItem = list.elementAt(i);

			if ((!contains(buddyItem))
					&& (buddyItem.toString().indexOf(last) >= 0)) {
				addElement(buddyItem);
			}
		}

	}

	/**
	 * Get the first buddies from roster.
	 * 
	 * @param tempRoster
	 *            The user's roster.
	 */
	public final void initialByRoster(final Roster tempRoster) {
		Collection<RosterEntry> rosterEntries = tempRoster.getEntries();

		list.clear();

		for (RosterEntry rosterEntry : rosterEntries) {
			// Only add buddies subscribe to each other to the list
			if (rosterEntry.getType() == RosterPacket.ItemType.both) {
				addBuddy(rosterEntry);
			}

		}
		refilter(null);
	}

	/**
	 * Deliver the received message.
	 * 
	 * @param message
	 *            The received message.
	 */
	public final void processMessage(final Message message) {

		if (message.getBody() != null && message.getBody().trim().length() > 0
				&& soundNotify) {
			// SoundPlayer should be a singleton, update latter
			// Skip blank message.
			SoundPlayer.playSound(null, 0);
		}

		BuddyItem buddyItem;
		buddyItem = getBuddyItemByUser(message.getFrom());
		if (buddyItem != null) {
			BuddyChat buddyChat = this.getBuddyChatByItem(buddyItem);
			buddyChat.recieve(message);

		}
	}

	/**
	 * Set online buddy only.
	 * 
	 * @param b
	 *            The new value
	 */
	public final void setOnlineOnly(final boolean b) {
		onlineOnly = b;

		refilter(null);
	}

	/**
	 * When a chat created. blind it to a certen chatCell.
	 * 
	 * @param chat
	 *            The created chat.
	 */
	public final void saveChat(final Chat chat) {
		BuddyItem buddyItem;
		buddyItem = getBuddyItemByUser(chat.getParticipant());

		if (buddyItem != null) {
			BuddyChat buddyChat = getBuddyChatByItem(buddyItem);
			buddyChat.setChat(chat);

			chatMap.put(buddyItem, chat);
		}
	}

	/**
	 * Get buddyItem by given user.
	 * 
	 * @param user
	 *            The given user.
	 * @return The buddyItem.
	 */
	public final BuddyItem getBuddyItemByUser(final String user) {
		BuddyItem buddyItem;
		for (int i = 0; i < list.size(); i++) {
			buddyItem = list.elementAt(i);

			if (user.indexOf(userMap.get(buddyItem)) >= 0) {
				return buddyItem;
			}
		}

		return null;
	}

	/**
	 * Get BuddyChat by the given BuddyItem.
	 * 
	 * @param buddyItem
	 *            The buddyItem.
	 * @return The buddyChat.
	 */
	public final BuddyChat getBuddyChatByItem(final BuddyItem buddyItem) {
		BuddyChat buddyChat = map.get(buddyItem);

		if (buddyChat == null) {
			buddyChat = new BuddyChat(buddyItem);
			map.put(buddyItem, buddyChat);
		}

		return buddyChat;
	}

	/**
	 * Get chat by buddy's name.
	 * 
	 * @param buddy
	 *            The buddy.
	 * @return The chat.
	 */
	public final Chat getChat(final String buddy) {
		BuddyItem buddyItem;
		for (int i = 0; i < list.size(); i++) {
			buddyItem = list.elementAt(i);

			if (userMap.get(buddyItem).indexOf(buddy) >= 0) {
				Chat chat = chatMap.get(buddyItem);

				if (chat == null) {
					chat = createChat(buddy);
					saveChat(chat);
				}

				return chat;
			}
		}
		return null;
	}

	/**
	 * Create chat by buddy 's name.
	 * 
	 * @param buddy
	 *            The buddy name.
	 * @return The created chat.
	 */
	public static Chat createChat(final String buddy) {
		return chatManager.createChat(buddy, null);
	}

	/**
	 * Set whether sound notify or not.
	 * 
	 * @param b
	 *            The new value.
	 */
	public final void setSoundNotify(final Boolean b) {
		soundNotify = b;
	}

	/**
	 * Get buddies' names.
	 * 
	 * @return The arraylist including all the names.
	 */
	public final ArrayList getUsers() {
		Object[] temp = userMap.values().toArray();
		ArrayList<String> names = new ArrayList<String>();

		for (Object o : temp) {
			names.add((String) o);
		}

		return names;
	}

}
